package cn.green.core.executor;

import cn.green.core.common.QTableConstant;
import cn.green.core.components.entity.JoinInfo;
import cn.green.core.model.QCondition;
import cn.green.core.model.QField;
import cn.green.core.model.QTable;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import cn.green.core.components.database.DBRouter;
import cn.green.core.components.ddao.QDql;
import cn.green.core.components.entity.DatabaseSql;
import cn.green.core.components.exceptions.QDqlException;
import cn.green.core.components.field.FieldTypeCom;
import cn.green.core.components.store.field.FieldTypeStore;
import cn.green.core.components.store.table.TableTypeStore;
import cn.green.core.components.table.TableTypeCom;
import cn.green.manage.cache.QCache;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author wang.yishu
 * @version 1.0
 * @className DqlExecutor
 * @description dql执行器
 * @date 2024/11/26 16:06
 */
public class DqlExecutor {

    public DqlExecutor(QDql dql, DBRouter dbRouter, QCache qCache) {
        this.qDql = dql;
        this.dbRouter = dbRouter;
        this.qCache = qCache;
    }

    private final QDql qDql;
    private final DBRouter dbRouter;
    private final QCache qCache;

    private QTable getQTable(String source, String scheme, String tableName) {
        QTable tableFromCache = qCache.getTableFromCache(source, scheme, tableName);
        if (tableFromCache == null) {
            throw new QDqlException("表结构不存在!");
        }
        List<QField> fieldsFromCache = qCache.getFieldsFromCache(source, scheme, tableName);
        if (fieldsFromCache == null) {
            throw new QDqlException("字段结构不存在!");
        }
        tableFromCache.setFields(fieldsFromCache);
        return tableFromCache;
    }

    private Map<String, JoinInfo> getSameDatabaseJoinInfoMap(QTable qTable) {
        List<QField> tableFields = qTable.getFields();
        Map<String, JoinInfo> joinInfos = new HashMap<>();
        for (QField tableField : tableFields) {
            String fieldType = tableField.getFieldType();
            FieldTypeCom fieldTypeCom;
            if (fieldType != null && !fieldType.isEmpty() && (fieldTypeCom = FieldTypeStore.get(fieldType)) != null) {
                JoinInfo itemJoinInfo = fieldTypeCom.joinInfos(qTable, tableField);
                if (itemJoinInfo != null && itemJoinInfo.getForeignTable().getSourceName().equals(qTable.getSourceName())
                        && itemJoinInfo.getForeignTable().getSchemeName().equals(qTable.getSchemeName())) {
                    List<QField> foreignFields = itemJoinInfo.getForeignFields();
                    for (QField foreignField : foreignFields) {
                        foreignField.setSelectColumnName(tableField.getFieldName() + "." + foreignField.getFieldName());
                    }
                    itemJoinInfo.setForeignTableAliasName(tableField.getFieldName());
                    joinInfos.put(tableField.getFieldName(), itemJoinInfo);
                    qTable.setNeedAs(true);
                    tableField.setSelectColumnName(QTableConstant.DQL.PRIMARY_TABLE + "." + tableField.getFieldName());
                    qTable.setAsTable(QTableConstant.DQL.PRIMARY_TABLE);
                }
            }
        }
        return joinInfos;
    }

    private Map<String, JoinInfo> getCrossDatabaseJoinInfoMap(QTable qTable) {
        List<QField> tableFields = qTable.getFields();
        Map<String, JoinInfo> joinInfos = new HashMap<>();
        for (QField tableField : tableFields) {
            String fieldType = tableField.getFieldType();
            FieldTypeCom fieldTypeCom;
            if (fieldType != null && !fieldType.isEmpty() && (fieldTypeCom = FieldTypeStore.get(fieldType)) != null) {
                JoinInfo itemJoinInfo = fieldTypeCom.joinInfos(qTable, tableField);
                if (itemJoinInfo != null && (
                        !itemJoinInfo.getForeignTable().getSourceName().equals(qTable.getSourceName()) || !itemJoinInfo.getForeignTable().getSchemeName().equals(qTable.getSchemeName())
                        )) {
                    itemJoinInfo.setForeignTableAliasName(tableField.getFieldName());
                    joinInfos.put(tableField.getFieldName(), itemJoinInfo);
                }
            }
        }
        return joinInfos;
    }

    /**
     * 查询连接表数据
     * @param joinTable 连接表信息
     * @return  数据
     */
    public List<Map<String, Object>> joinList(QTable joinTable, List<QCondition> conditions) {
        DatabaseSql databaseSql = this.dbRouter.get(joinTable.getSourceName()).getDqlDialect().selectListSql(joinTable, conditions);
        databaseSql.setDatabase(this.dbRouter.get(joinTable.getSourceName()));
        return this.qDql.selectList(databaseSql);
    }

    public List<Map<String, Object>> joinList(QTable joinTable, JSONObject param) {
        param = param == null ? new JSONObject() : param;
        DatabaseSql databaseSql = this.dbRouter.get(joinTable.getSourceName()).getDqlDialect().selectListSql(joinTable,null, null ,param);
        databaseSql.setDatabase(this.dbRouter.get(joinTable.getSourceName()));
        return this.qDql.selectList(databaseSql);
    }

    /**
     * 查询列表
     * @param scheme    表空间
     * @param tableName 表名
     * @param param     查询参数
     * @return 结果集
     */
    //done
    public JSONArray simpleList(String source, String scheme, String tableName, JSONObject param) {
        param = param == null ? new JSONObject() : param;
        QTable qTable = this.getQTable(source, scheme, tableName);
        //找到在一个库里的连接信息
        Map<String, JoinInfo> joinInfoMap = this.getSameDatabaseJoinInfoMap(qTable);
        //找到需要表连接却因为不在一个库里的连接信息
        Map<String, JoinInfo> notJoinInfoMap = this.getCrossDatabaseJoinInfoMap(qTable);
        Map<String, QField> notJoinFieldMap = new HashMap<>();
        for (QField field : qTable.getFields()) {
            if (!joinInfoMap.isEmpty()) {
                field.setSelectColumnName(QTableConstant.DQL.PRIMARY_TABLE + "." + field.getFieldName());
            }
            if (!notJoinInfoMap.isEmpty()) {
                if (notJoinInfoMap.containsKey(field.getFieldName())) {
                    notJoinFieldMap.put(field.getFieldName(), field);
                }
            }
        }
        DatabaseSql databaseSql = this.dbRouter.get(source).getDqlDialect().selectListSql(qTable, joinInfoMap, notJoinInfoMap, param);
        databaseSql.setDatabase(this.dbRouter.get(source));
        AtomicReference<List<Map<String, Object>>> data = new AtomicReference<>(this.qDql.selectList(databaseSql));
        if (data.get() != null && !data.get().isEmpty()) {
            for (Map<String, Object> map : data.get()) {
                Set<String> keys = map.keySet();
                Map<String, Object> newDataMap = new HashMap<>();
                List<String> removeKeys = new ArrayList<>();
                for (String key : keys) {
                    if (key.contains(QTableConstant.DQL.PRIMARY_TABLE)) {
                        removeKeys.add(key);
                        String newKey = key.replaceAll(QTableConstant.DQL.PRIMARY_TABLE + "\\.", "");
                        newDataMap.put(newKey, map.get(key));
                    }
                }
                map.putAll(newDataMap);
                for (String removeKey : removeKeys) {
                    map.remove(removeKey);
                }
            }
        }
        //直接查询
        if (!notJoinInfoMap.isEmpty()) {
            JSONObject finalParam = param;
            notJoinInfoMap.forEach((fieldName, joinInfo) -> {
                QField field = notJoinFieldMap.get(fieldName);
                String fieldType = field.getFieldType();
                //todo 跨库连表查询的查询条件逻辑还需要再修改
                List<QCondition> conditions;
                if (fieldType != null && !fieldType.isEmpty()) {
                    FieldTypeCom fieldTypeCom = FieldTypeStore.get(fieldType);
                    if (fieldTypeCom != null) {
                        conditions = fieldTypeCom.conditions(field, finalParam);
                    } else {
                        conditions = new ArrayList<>();
                    }
                } else {
                    conditions = new ArrayList<>();
                }
                QTable foreignTable = joinInfo.getForeignTable();
                List<QField> foreignFields = joinInfo.getForeignFields();
                List<JoinInfo.JoinField> joinFields = joinInfo.getJoinFields();
                if (joinFields == null || joinFields.isEmpty()) {
                    return;
                }
                joinFields.forEach(joinField -> {
                    if (foreignFields.stream().noneMatch(item ->
                            item.getTableName().equals(joinField.getForeignTableField().getTableName())
                         && item.getSourceName().equals(joinField.getForeignTableField().getSourceName())
                         && item.getSchemeName().equals(joinField.getForeignTableField().getSchemeName())
                         && item.getFieldName().equals(joinField.getForeignTableField().getFieldName()))) {
                        foreignFields.add(joinField.getForeignTableField());
                    }
                });
                //todo 跨库连表查询的查询条件逻辑还需要再修改
                List<QCondition> joinConditions = new ArrayList<>();
                foreignFields.forEach(foreignTableField -> {
                    List<QCondition> collect = conditions.stream().filter(item ->
                               item.getConditionField().getFieldName().equals(foreignTableField.getFieldName())
                            && item.getConditionField().getTableName().equals(foreignTableField.getTableName())
                            && item.getConditionField().getSourceName().equals(foreignTableField.getSourceName())
                            && item.getConditionField().getSchemeName().equals(foreignTableField.getSchemeName())
                    ).collect(Collectors.toList());
                    joinConditions.addAll(collect);
                });
                foreignTable.setFields(foreignFields);
                //关联类型
                JoinInfo.JoinType joinType = joinInfo.getJoinType();
                //关联信息
                List<Map<String, Object>> joinData = this.joinList(foreignTable, joinConditions);
                switch (joinType) {
                    case INNER:
                        data.set(joinInfo.rangeInnerJoinData(data.get(), joinData));
                        break;
                    case RIGHT:
                        data.set(joinInfo.rangeRightJoinData(data.get(), joinData));
                        break;
                    case LEFT:
                    default:
                        data.set(joinInfo.rangeLeftJoinData(data.get(), joinData));
                        break;
                }
            });
        }

        return JSONArray.parseArray(JSONArray.toJSONString(data));
    }

    /**
     * 查询格式化后的列表(全部列表)
     * @param table     表结构
     * @param param     查询参数
     * @return 结果集
     */
    public JSONArray formatList(QTable table, JSONObject param) {
        JSONArray data = this.simpleList(table.getSourceName(), table.getSchemeName(), table.getTableName(), param);
        JSONArray rtn = new JSONArray();
        if (data != null && !data.isEmpty()) {
            for (int i = 0; i < data.size(); i++) {
                JSONObject item = data.getJSONObject(i);
                JSONObject formatData = this.formatData(table, JSONObject.parseObject(JSONObject.toJSONString(item)));
                rtn.add(formatData);
            }
        }
        return rtn;
    }

    public JSONObject formatData(QTable table, JSONObject origin) {
        JSONObject data = new JSONObject();
        table.getFields().forEach(field -> {
            String fieldName = field.getFieldName();
            if (origin.containsKey(fieldName)) {
                data.put(fieldName, origin.get(fieldName));
                origin.remove(fieldName);
            } else {
                if (origin.containsKey(QTableConstant.DQL.PRIMARY_TABLE + "." + fieldName)) {
                    data.put(fieldName, origin.get(QTableConstant.DQL.PRIMARY_TABLE + "." + fieldName));
                    origin.remove(QTableConstant.DQL.PRIMARY_TABLE + "." + fieldName);
                }
            }
            origin.keySet().forEach(key -> {
                if (key.startsWith(fieldName + ".")) {
                    data.put(key, origin.get(key));
                }
            });
            String fieldType = field.getFieldType();
            if (fieldType != null && !fieldType.isEmpty()) {
                FieldTypeCom fieldTypeCom = FieldTypeStore.get(fieldType);
                if (fieldTypeCom != null) {
                    fieldTypeCom.formatRecord(data, field);
                }
            }
        });
        return data;
    }

    /**
     * 查询列表
     * @param scheme    表空间
     * @param tableName 表名
     * @param param     查询条件
     * @return 数据集
     */
    public JSONArray select(String source, String scheme, String tableName, JSONObject param) {
        QTable qTable = this.getQTable(source, scheme, tableName);
        JSONArray array = this.formatList(qTable, param);
        String tableType = qTable.getTableType();
        TableTypeCom tableTypeCom = TableTypeStore.get(tableType);
        if (tableTypeCom != null) {
            return tableTypeCom.arrangeData(array, qTable, param);
        }
        return null;
    }


    /**
     * 分页查询
     * 注意：page方法只能适用于列表结构、树结构无法使用
     * @param scheme    表空间
     * @param tableName 表名
     * @param pageNo    页码
     * @param pageSize  页面尺寸
     * @param param     查询参数
     * @return          数据集
     */
    public JSONObject page(String source, String scheme, String tableName, Integer pageNo, Integer pageSize, JSONObject param) {
        JSONObject rtn = new JSONObject();
        JSONArray formatData = new JSONArray();
        JSONArray originData = this.simpleList(source, scheme, tableName, param);
        int total = 0;
        if (originData != null && !originData.isEmpty() && pageNo != null && pageSize != null) {
            total = originData.size();
            originData = originData.subList((pageNo - 1) * pageSize, Math.min((pageNo * pageSize), originData.size())).stream().collect(Collectors.toCollection(JSONArray::new));
            if (!originData.isEmpty()) {
                for (int i = 0; i < originData.size(); i++) {
                    JSONObject item = originData.getJSONObject(i);
                    JSONObject formated = this.formatData(this.getQTable(source, scheme, tableName), item);
                    formatData.add(formated);
                }
            }
        }
        rtn.put("total", total);
        rtn.put("data", formatData);
        return rtn;
    }

    /**
     * 查询明细
     * @param scheme    表空间
     * @param tableName 表名
     * @param primaryVal 主键值
     * @return  数据
     */
    public JSONObject detail(String source, String scheme, String tableName, Object primaryVal) {
        QTable qTable = this.getQTable(source, scheme, tableName);
        Map<String, JoinInfo> joinInfoMap = this.getSameDatabaseJoinInfoMap(qTable);
        //找到需要表连接却因为不在一个库里的连接信息
        Map<String, JoinInfo> notJoinInfoMap = this.getCrossDatabaseJoinInfoMap(qTable);
        for (QField field : qTable.getFields()) {
            if (!joinInfoMap.isEmpty()) {
                field.setSelectColumnName(QTableConstant.DQL.PRIMARY_TABLE + "." + field.getFieldName());
            } else {
                field.setSelectColumnName(field.getFieldName());
            }
        }
        DatabaseSql databaseSql = this.dbRouter.get(qTable.getSourceName()).getDqlDialect().selectOneSql(qTable, joinInfoMap, primaryVal);
        databaseSql.setDatabase(this.dbRouter.get(source));
        Map<String, Object> one = this.qDql.selectOne(databaseSql);
        AtomicReference<List<Map<String, Object>>> data = new AtomicReference<>(Collections.singletonList(one));
        //直接查询
        if (!notJoinInfoMap.isEmpty()) {
            Map<String, Object> finalOne = one;
            notJoinInfoMap.forEach((fieldName, joinInfo) -> {
                QTable foreignTable = joinInfo.getForeignTable();
                List<QField> foreignFields = joinInfo.getForeignFields();
                List<JoinInfo.JoinField> joinFields = joinInfo.getJoinFields();
                if (joinFields == null || joinFields.isEmpty()) {
                    return;
                }
                JSONObject joinParam = new JSONObject();
                joinFields.forEach(joinField -> {
                    QField primaryTableField = joinField.getPrimaryTableField();
                    QField foreignTableField = joinField.getForeignTableField();
                    joinParam.put(foreignTableField.getFieldName(), finalOne.get(joinInfoMap.isEmpty() ? primaryTableField.getFieldName()
                            : QTableConstant.DQL.PRIMARY_TABLE + "." + primaryTableField.getFieldName()));
                    foreignFields.add(foreignTableField);
                });
                foreignTable.setFields(foreignFields);
                //关联类型
                JoinInfo.JoinType joinType = joinInfo.getJoinType();
                //关联信息
                List<Map<String, Object>> joinData = this.joinList(foreignTable, joinParam);
                switch (joinType) {
                    case INNER:
                        data.set(joinInfo.rangeInnerJoinData(data.get(), joinData));
                        break;
                    case RIGHT:
                        data.set(joinInfo.rangeRightJoinData(data.get(), joinData));
                        break;
                    case LEFT:
                    default:
                        data.set(joinInfo.rangeLeftJoinData(data.get(), joinData));
                        break;
                }
            });
        }

        one = data.get().get(0);
        JSONObject oneJson = JSONObject.parseObject(JSONObject.toJSONString(one));
        if (one != null && !one.isEmpty()) {
            JSONObject formatData = this.formatData(qTable, oneJson);
            String tableType = qTable.getTableType();
            TableTypeCom tableTypeCom = TableTypeStore.get(tableType);
            if (tableTypeCom != null) {
                return tableTypeCom.arrangeDetail(formatData, qTable);
            }
            return oneJson;
        }
        return new JSONObject();
    }
}
